/** @file    ButlerImpl.cpp
 *  @author  Alessandro Polo
 *  @version $Id: ButlerImpl.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the ButlerImpl class.
 * The header for this class can be found in ButlerImpl.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "ButlerImpl.h"

 #include <core/Message.h>
 #include <core/MethodsCommon.h>
 #include <core/Request.h>
 #include <core/UserManager.h>
 #include <core/SecurityManager.h>

 #include <stdlib.h>
 #include <time.h>
 #include <algorithm>

using namespace std;
using namespace wosh;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

ButlerImpl::ButlerImpl( BundleGeneric& bundle ) : BundleGenericWorker(bundle)
 {
	this->listener = NULL;
	this->doorBellsNotify = 1;
	this->doorBellsPriority = Notification::PRIORITY_DEFAULT;
 }


ButlerImpl::~ButlerImpl() {
	if ( isThreadRunning() ) {
		quitThread(10000);
	 }

 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD

void ButlerImpl::runThread()
 {
	Log(LOG_VERBOSE, ":runThread(): STARTING" );

	updateDoorBells();

	while( this->running )
	 {
		ThreadImpl::sleepForMSec(50);
		setThreadAlive();

		if ( this->msgQueue.isEmpty() ) {
			ThreadImpl::sleepForMSec(50);
			continue;
		 }

		// get and remove it from queue
		Message* message = this->msgQueue.pop();
		if ( message == NULL ) continue;

		evalMessage(message);

		// finally delete the instance
		delete message;
	 }

	Log(LOG_VERBOSE, ":runThread(): EXITING" );
 }

////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ButlerImpl::updateDoorBells()
 {
	DataList* dl1 = new DataList();
	dl1->add( "type="_DoorBell_TYPE );
	dl1->add( _BuildingObject_KEY_Location );
	dl1->add( _KEY_owner );
	Request* request = new Request(_BuildingManager_METHOD_getchildprop, dl1);
	if ( this->listener != NULL )
		this->listener->updateObject(request, "*:Building", this);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ButlerImpl::evalResponse( const Message& message )
 {
	// add/remove/ update info of communicator/interpreter
	if ( message.isEmpty() ) return;
	if ( !message.getContent()->isResponse() ) return;
	const Response* response = message.getContent()->asResponse();
	if ( !response->hasData() ) return;

	if ( response->getMethod() == _BuildingManager_METHOD_getchildprop ) {
		if ( !response->getData()->isKindOf<Table>() ) return;
		const wosh::Table* childrenprops = response->getData()->as<Table>();
		if ( childrenprops->getTableName() == "Properties of type="_DoorBell_TYPE  )
			evalDoorBells( childrenprops, message.getSource() );
	 }

 }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ButlerImpl::evalMessageIncoming( const Message& message )
 {
	if ( message.isEmpty() ) return;
	if ( !message.getContent()->isFact() ) return;
	msgQueue.push( message.clone() );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ButlerImpl::evalMessage( Message* message )
 {
	if ( message == NULL || message->isEmpty() ) return;

	if ( message->getContent()->isFact() ) {
		const Fact* fact = message->getContent()->asFact();

		if ( fact->getEvent() == _BuildingObject_EVENT_StateChanged ) {
			if ( !fact->hasData() || !fact->getData()->isKindOf<Variant>() )
				return;
			const Data* dataEvent = dynamic_cast<const Data*>(fact->getData());

			this->doorBells.mutex().lockForRead();
			DoorBellInfo* dBell = this->doorBells.key_( message->getSource().toString() );
			if ( dBell != NULL ) {
				DoorBell::BELL_STATE state = DoorBell::getStateFromString( dataEvent->getData() );
				evalDoorBellEvent( dBell, state );
				this->doorBells.mutex().unLock();
				return;
			 }
			this->doorBells.mutex().unLock();
		 }
		else if ( fact->getEvent() == _UserManager_EVENT_UsersAtHome ) {
			if ( !fact->hasData() || !fact->getData()->isKindOf<Variant>() )
				return;
			const Data* dataEvent = dynamic_cast<const Data*>(fact->getData());
			evalUsersAtHomeEvent( dataEvent->toInteger() );
		 }

	 }

 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ButlerImpl::evalDoorBells( const Table* childrenprops, const URI& )
 {
	this->doorBells.mutex().lockForWrite();
	for(unsigned int row=0; row<childrenprops->getRows(); row++ ) {
		const ISerializable* objURI = childrenprops->get(row, 0);
		const ISerializable* objKey = childrenprops->get(row, 1);
		const ISerializable* objValue = childrenprops->get(row, 2);
		if ( objURI == NULL || !objURI->isKindOf<Variant>() ) continue;
		if ( objKey == NULL || !objKey->isKindOf<Variant>() ) continue;
		if ( objValue == NULL || !objValue->isKindOf<Variant>() ) continue;
		const wosh::Data* dataURI = dynamic_cast<const wosh::Data*>(objURI);
		const wosh::Data* dataKey = dynamic_cast<const wosh::Data*>(objKey);
		const wosh::Data* dataValue = dynamic_cast<const wosh::Data*>(objValue);

		DoorBellInfo* dBell = this->doorBells.key_(dataURI->getData());
		if ( dBell == NULL ) {
			dBell = new DoorBellInfo();
			dBell->uri.fromString(dataURI->getData());
			this->doorBells.set_(dataURI->getData(), dBell);
		 }

		if ( dataKey->getData() == _KEY_owner )
			dBell->owner = dataValue->getData();

		else if ( dataKey->getData() == _BuildingObject_KEY_Location ) {
			Utilities::extractWordFromString( dataValue->getData(), dBell->homename, "/" );
		 }
		
	 }

	this->doorBells.mutex().unLock();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ButlerImpl::evalDoorBellEvent( DoorBellInfo* dBell, DoorBell::BELL_STATE state )
 {
	if ( dBell == NULL ) return;

	long lastEvent = dBell->getLastEvent();
	long now = Utilities::std_time();
	dBell->history[ now ] = state;

	if ( state != DoorBell::BELL_RINGING ) {
		// ignore such events
		Log(LOG_VERBOSE, ":evalDoorBellEvent(%s): Ignored Event %s", dBell->uri.toString().c_str(), DoorBell::getStateAsString(state).c_str() );
		return;
	 }

	if ( this->doorBellsNotify == 0 ) {
		// ignore
		Log(LOG_VERBOSE, ":evalDoorBellEvent(%s): Ignored [disabled]", dBell->uri.toString().c_str() );
		return;
	 }
	else if ( this->doorBellsNotify != 1 ) {
		int users = UserManager::getUserCountAtHome();
		if ( users > 0 ) {
			// ignore, house is not empty
			Log(LOG_VERBOSE, ":evalDoorBellEvent(%s): Ignored [%d users at home]", dBell->uri.toString().c_str(), users );
			return;
		 }
	 }

	if ( now - lastEvent < 20 ) {
		// ignore, we just notified that
		Log(LOG_VERBOSE, ":evalDoorBellEvent(%s): Ignored [already triggered]", dBell->uri.toString().c_str() );
		return;
	 }

	if ( dBell->owner.empty() ) {
		Log(LOG_WARNING, ":evalDoorBellEvent(%s): DoorBell's Owner UNSET!", dBell->uri.toString().c_str() );
		return;
	 }

	Log(LOG_INFO, ":evalDoorBellEvent(%s): Raising Notification for %s", dBell->uri.toString().c_str(), dBell->owner.c_str() );

	TextMessage* txtmsg = new TextMessage();
	txtmsg->setSender_User( _Butler_USER );
	txtmsg->setRecipent_User( dBell->owner );
	txtmsg->setProtocol( Notification::PROTOCOL_HUMAN );
	txtmsg->setPriority( this->doorBellsPriority );

	txtmsg->setLanguage( "en_GB" );
	if ( !dBell->homename.empty() )
		txtmsg->setSubject( "Butler of " + dBell->homename );
	txtmsg->setPlain( "DoorBell is Ringing." );


	Message* msg_event = new Message( txtmsg );
	msg_event->setSource( this->getOwner()->getURI() );
	msg_event->setDestinationBroadcast(true);
	msg_event->setSecurityToken( SecurityManager::generateToken(_Butler_USER) );
	BusCore.postMessage(msg_event);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ButlerImpl::evalUsersAtHomeEvent( int count )
 {
	Log(LOG_INFO, ":evalUsersAtHomeEvent(%d)", count );

	if ( count == 0 ) {

	 }
	else {

	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
